/*! ========================================================================
** Extended Template and Library
** State Machine Abstraction Class Implementation
**
** Copyright (c) 2002 Robert B. Quattlebaum Jr.
** Copyright (c) 2008 Chris Moore
**
** This package is free software; you can redistribute it and/or
** modify it under the terms of the GNU General Public License as
** published by the Free Software Foundation; either version 2 of
** the License, or (at your option) any later version.
**
** This package is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
** General Public License for more details.
**
** === N O T E S ===========================================================
**
** ========================================================================= */

#ifndef __ETL__SMACH_H_
#define __ETL__SMACH_H_

#include <vector>
#include <algorithm>
#include <stdexcept>
#include "_mutex_null.h"
#include "_misc.h"

#define SMACH_STATE_STACK_SIZE		(32)

#ifdef _MSC_VER
#pragma warning (disable:4786)
#pragma warning (disable:4290) // MSVC6 doesn't like function declarations with exception specs
#endif

#define ETL_MUTEX_LOCK()

_ETL_BEGIN_NAMESPACE

template <typename CON, typename K = int, typename M = mutex_null>
class smach
{
public:

    typedef K event_key;
    typedef M _mutex;
    typedef CON context_type;

    struct egress_exception { };
    struct pop_exception { };

    // Result type for event processing
    enum event_result {
        // These values are returned by the event
        // handlers cast to state pointers.
        RESULT_ERROR,		// < General error or malfunction
        RESULT_OK,			// < Event has been processed
        RESULT_ACCEPT,		// < The event has been explicitly accepted.
        RESULT_REJECT,		// < The event has been explicitly rejected.

        RESULT_END			// < Not a valid result
    };


    // Event base class
    struct event {
        event_key key;

        event() { }
        event(const event_key& key): key(key) { }

        operator event_key()const
        {
            return key;
        }
    };

    // Event definition class
    template<typename T>
    class event_def_internal
    {
        // List our friends
        friend class smach;

    public:
        typedef T state_context_type;

        // Event function type
        typedef event_result(T::*funcptr)(const event&);


        event_key id;		// <! Event ID
        funcptr handler;	// <! Pointer event handler

    public:

        // Less-than operator for sorting. Based on event_key value.
        bool operator<(const event_def_internal &rhs)const
        {
            return id < rhs.id;
        }

        // Equal-to operator. Based on event_key value.
        bool operator==(const event_def_internal &rhs)const
        {
            return id == rhs.id;
        }

        // Less-than operator for finding.
        bool operator<(const event_key &rhs)const
        {
            return id < rhs;
        }

        // Equal-to operator. Based on event_key value.
        bool operator==(const event_key &rhs)const
        {
            return id == rhs;
        }

        // Trivial Constructor
        event_def_internal() { }

        // Constructor for creating an event_def_internal from the given key and function reference.
        event_def_internal(event_key a, funcptr b): id(a), handler(b) { }

        // Copy constructor
        event_def_internal(const event_def_internal &x): id(x.id), handler(x.handler) { }

    };

    class state_base
    {
        // Our parent is our friend
        friend class smach;
    public:
        virtual ~state_base() { }

        virtual void* enter_state(context_type* machine_context)const = 0;

        virtual bool leave_state(void* state_context)const = 0;

        virtual event_result process_event(void* state_context, const event& id)const = 0;

        virtual const char *get_name() const = 0;
    };

    // State class
    template<typename T>
    class state : public state_base
    {
        // Our parent is our friend
        friend class smach;

    public:
        typedef event_def_internal<T> event_def;
        typedef T state_context_type;

    private:

        std::vector<event_def> event_list;

        smach *nested;		// Nested machine
        event_key low, high;	// Lowest and Highest event values
        const char *name;	// Name of the state
        typename event_def::funcptr default_handler;	// Default handler for unknown key

    public:

        // Constructor
        state(const char *n, smach* nest = 0):
            nested(nest), name(n), default_handler(NULL)
        { }

        virtual ~state() { }

        // Setup a nested state machine
        /*! A more detailed explanation needs to be written */
        void set_nested_machine(smach *sm)
        {
            nested = sm;
        }

        // Sets the default handler
        void set_default_handler(const typename event_def::funcptr &x)
        {
            default_handler = x;
        }

        // Returns given the name of the state
        virtual const char *get_name() const
        {
            return name;
        }

        // Adds an event_def onto the list and then make sure it is sorted correctly.
        void
        insert(const event_def &x)
        {
            // If this is our first event_def,
            // setup the high and low values.
            if (!event_list.size()) {
                low = high = x.id;
            }

            // Sort the event_def onto the list
            event_list.push_back(x);
            sort(event_list.begin(), event_list.end());

            // Update the low and high markers
            if (x.id < low) {
                low = x.id;
            }

            if (high < x.id) {
                high = x.id;
            }
        }

        typename std::vector<event_def>::iterator find(const event_key &x)
        {
            return binary_find(event_list.begin(), event_list.end(), x);
        }
        typename std::vector<event_def>::const_iterator find(const event_key &x)const
        {
            return binary_find(event_list.begin(), event_list.end(), x);
        }

    protected:

        virtual void* enter_state(context_type* machine_context)const
        {
            return new state_context_type(machine_context);
        }

        virtual bool leave_state(void* x)const
        {
            state_context_type* state_context(reinterpret_cast<state_context_type*>(x));
            delete state_context;
            return true;
        }

        virtual event_result
        process_event(void* x, const event& id)const
        {
            state_context_type* state_context(reinterpret_cast<state_context_type*>(x));

            // Check for nested machine in state
            if (nested) {
                const event_result ret(nested->process_event(id));

                if (ret != RESULT_OK) {
                    return ret;
                }
            }

            // Quick test to make sure that the
            // given event is in the state
            if (id.key < low || high < id.key) {
                return RESULT_OK;
            }

            // Look for the event
            typename std::vector<event_def>::const_iterator iter(find(id.key));

            // If search results were negative, fail.
            if (iter->id != id.key) {
                return RESULT_OK;
            }

            // Execute event function
            event_result ret((state_context->*(iter->handler))(id));

            if (ret == RESULT_OK && default_handler) {
                ret = (state_context->*(default_handler))(id);
            }

            return ret;
        }
    };

private:

    // Machine data
    const state_base* curr_state;	// < Current state of the machine
    smach* child;				   // < Child machine

public: // this really should be private
    void* state_context;		// < State Context
private:

    context_type* machine_context;		// < Machine Context

    const state_base* default_state;
    void*	default_context;

#ifdef ETL_MUTEX_LOCK
    _mutex mutex;
#endif

    // State stack data
    const state_base* 	state_stack[SMACH_STATE_STACK_SIZE];
    void* 				state_context_stack[SMACH_STATE_STACK_SIZE];
    int 				states_on_stack;

public:

    // Gets the name of the currently active state
    const char *
    get_state_name()const
    {
#ifdef ETL_MUTEX_LOCK
        ETL_MUTEX_LOCK();
#endif

        if (curr_state) {
            return curr_state->get_name();
        }

        if (default_state) {
            return default_state->get_name();
        }

        return 0;
    }

    // Determines if a given event result is an error
    /*! This function allows us to quickly see
    	if an event_result contained an error */
    static bool
    event_error(const event_result &rhs)
    {
        return rhs <= RESULT_ERROR;
    }

    bool
    set_default_state(const state_base *nextstate)
    {
#ifdef ETL_MUTEX_LOCK
        ETL_MUTEX_LOCK();
#endif
        // Keep track of the current state unless
        // the state switch fails
        const state_base *prev_state = default_state;

        // If we are already in a state, leave it and
        // collapse the state stack
        if (default_state) {
            default_state->leave_state(default_context);
        }

        // Set this as our current state
        default_state = nextstate;
        default_context = 0;

        // Attempt to enter the state
        if (default_state) {
            default_context = default_state->enter_state(machine_context);

            if (default_context) {
                return true;
            }
        } else {
            return true;
        }

        // We failed, so attempt to return to previous state
        default_state = prev_state;

        // If we had a previous state, enter it
        if (default_state) {
            default_context = default_state->enter_state(machine_context);
        }

        // At this point we are not in the
        // requested state, so return failure
        return false;
    }

    // Leaves the current state
    /*! Effectively makes the state_depth() function return zero. */
    bool
    egress()
    {
#ifdef ETL_MUTEX_LOCK
        ETL_MUTEX_LOCK();
#endif

        // Pop all states off the state stack
        while (states_on_stack) {
            pop_state();
        }

        // If we are not in a state, then I guess
        // we were successful.
        if (!curr_state) {
            return true;
        }

        // Grab the return value from the exit function
        bool ret = true;

        const state_base* old_state = curr_state;
        void *old_context = state_context;

        // Clear out the current state and its state_context
        curr_state = 0;
        state_context = 0;

        // Leave the state
        return old_state->leave_state(old_context);

        return ret;
    }

    // State entry function
    /*! Attempts to enter the given state,
    	popping off all states on the stack
    	in the process. */
    bool
    enter(const state_base *nextstate)
    {
#ifdef ETL_MUTEX_LOCK
        ETL_MUTEX_LOCK();
#endif

        // Keep track of the current state unless
        // the state switch fails
        const state_base *prev_state = curr_state;

        // If we are already in a state, leave it and
        // collapse the state stack
        if (curr_state) {
            egress();
        }

        // Set this as our current state
        curr_state = nextstate;
        state_context = 0;

        // Attempt to enter the state
        state_context = curr_state->enter_state(machine_context);

        if (state_context) {
            return true;
        }

        // We failed, so attempt to return to previous state
        curr_state = prev_state;

        // If we had a previous state, enter it
        if (curr_state) {
            state_context = curr_state->enter_state(machine_context);
        }

        // At this point we are not in the
        // requested state, so return failure
        return false;
    }

    // Pushes state onto state stack
    /*! This allows you to enter a state without
    	leaving your current state.
    	\param   nextstate Pointer to the state to enter
    	\sa      pop_state()
    */
    bool
    push_state(const state_base *nextstate)
    {
#ifdef ETL_MUTEX_LOCK
        ETL_MUTEX_LOCK();
#endif

        // If there are not enough slots, then throw something.
        if (states_on_stack == SMACH_STATE_STACK_SIZE) {
            throw (std::overflow_error("smach<>::push_state(): state stack overflow!"));
        }

        // If there is no current state, nor anything on stack,
        // just go ahead and enter the given state.
        if (!curr_state && !states_on_stack) {
            return enter(nextstate);
        }

        // Push the current state onto the stack
        state_stack[states_on_stack] = curr_state;
        state_context_stack[states_on_stack++] = state_context;

        // Make the next state the current state
        curr_state = nextstate;

        // Try to enter the next state
        state_context = curr_state->enter_state(machine_context);

        if (state_context) {
            return true;
        }

        // Unable to push state, return to old one
        curr_state = state_stack[--states_on_stack];
        state_context = state_context_stack[states_on_stack];
        return false;
    }

    // Pops state off of state stack
    /*! Decreases state depth */
    void
    pop_state()
    {
#ifdef ETL_MUTEX_LOCK
        ETL_MUTEX_LOCK();
#endif

        // If we aren't in a state, then there is nothing
        // to do.
        if (!curr_state) {
            throw (std::underflow_error("smach<>::pop_state(): stack is empty!"));
        }

        if (states_on_stack) {
            const state_base* old_state = curr_state;
            void *old_context = state_context;

            // Pop previous state off of stack
            --states_on_stack;
            curr_state = state_stack[states_on_stack];
            state_context = state_context_stack[states_on_stack];

            old_state->leave_state(old_context);
        } else { // If there are no states on stack, just egress
            egress();
        }
    }

    // State Machine Constructor
    /*! A more detailed description needs to be written */
    smach(context_type* machine_context = 0):
        curr_state(0),
        child(0),
        state_context(0),
        machine_context(machine_context),
        default_state(0),
        default_context(0),
        states_on_stack(0)
    { }

    // The destructor
    ~smach()
    {
        egress();

        if (default_state) {
            default_state->leave_state(default_context);
        }
    }

    // Sets up a child state machine
    /*! A child state machine runs in parallel with
    	its parent, and gets event priority. This
    	mechanism is useful in cases where an inherited
    	object has its own state machine. */
    void set_child(smach *x)
    {
#ifdef ETL_MUTEX_LOCK
        ETL_MUTEX_LOCK();
#endif
        child = x;
    }

    // Returns the number states currently active
    int
    state_depth()
    {
        return curr_state ? states_on_stack + 1 : 0;
    }

    event_result
    process_event(const event_key& id)
    {
        return process_event(event(id));
    }

    // Process an event
    event_result
    process_event(const event& id)
    {
#ifdef ETL_MUTEX_LOCK
        ETL_MUTEX_LOCK();
#endif

        event_result ret(RESULT_OK);

        // Check for child machine
        if (child) {
            ret = child->process_event(id);

            if (ret != RESULT_OK) {
                return ret;
            }
        }

        try {
            if (curr_state) {
                ret = curr_state->process_event(state_context, id);
            }

            if (ret == RESULT_OK) {
                return default_state->process_event(default_context, id);
            }

            return ret;
        } catch (egress_exception) {
            if (egress()) {
                ret = RESULT_ACCEPT;
            } else {
                ret = RESULT_ERROR;
            }
        } catch (pop_exception) {
            pop_state();
            return RESULT_ACCEPT;
        } catch (const state_base* state) {
            return enter(state) ? RESULT_ACCEPT : RESULT_ERROR;
        }

        return ret;
    }

}; // END of template class smach

_ETL_END_NAMESPACE

#endif